import java.io.IOException;
import java.util.*;

import javax.swing.DebugGraphics;


public class JayaLeeAssembler extends Assembler {

	InstructionSet instructions;
	RegisterSet registers;
	HashMap<String, Integer> dataLabels = new HashMap<String, Integer>();
	HashMap<String, Integer> textLabels = new HashMap<String, Integer>();
	String[] zeroPad={"","0","00","000","0000","00000","000000","0000000","00000000","000000000"};

	public JayaLeeAssembler() {
	}

	public JayaLeeAssembler(String[] args) throws IOException {
		super(args);
	}

	/*
	 *	You have to implement it to process the labels. You may also need to 
	 *	implement a symbol table to maintain the labels and their real addresses. 
	 */
	void processLabel(String sourceCode) {
		if (currentCodeSection == 0) {
			if (debug) {
				System.out.println("putting " + sourceCode + " in .text section with address: " + programCounter);
			}
			textLabels.put(sourceCode, programCounter);
		} else if (currentCodeSection == 1) {
			if (debug) {
				System.out.println("putting " + sourceCode + " in .data section with address: " + dataMemoryAddress);
			}
			dataLabels.put(sourceCode, dataMemoryAddress);
		} else {
			System.out.println("WTF DID YOU DO?! SECTION ERROR");
		}
	}

	/*
	 * You have to implement it for updating the programCounter. 
	 * If your algorithm does not require the variable, 
	 * you may implement it as an empty method. 
	 */
	void updateProgramCounter(Instruction instruction) {
		programCounter++;
		if (debug) {
			System.out.println("program counter is now " + programCounter);
		}
	}

	/*
	 * The method is called before the source code is scanned or generated. 
	 * You have to implement it to initialize some variables, such as file 
	 * descriptors or local variables, in your class.
	 */
	void initialization() throws IOException {
		instructions = new InstructionSet();
		registers = new RegisterSet();
		if (debug) {
			System.out.println("created InstructionSet and RegisterSet");
		}
	}

	/*
	 * Replace the operands in the form of labels with immediate values. 
	 * The method will be called before generating the machine code for 
	 * an instruction in the AssembleCode method. 
	 */
	void replaceInstructionLabel(Instruction instruction) {
		String[] branch = {"call", "be", "bne", "bl", "ble", "bg", "bge", "ba", "st", "ld", "mov"};
		// we may not need this depending on our design.
		for (int x=0; x<branch.length; x++) {
			if (instruction.operator.equals(branch[x])){
				for (int y=0; y<instruction.operands.length; y++) {
					Integer offset = textLabels.get(instruction.operands[y].name);
					System.out.println("offset of " + instruction.operands[y].name + " is "  + offset);
					if (offset != null) {
						if (debug) {
							System.out.println("replacing label " + instruction.operands[y].name + " with " + Integer.toString(offset,10));
						}
						instruction.operands[y].name = "addressl:" + Integer.toString(offset,10);
					} else {
						offset = dataLabels.get(instruction.operands[y].name);
						if (offset != null) {
							if (debug) {
								System.out.println("replacing label " + instruction.operands[y].name + " with " + offset);
							}
							instruction.operands[y].name = "addressd:" + Integer.toBinaryString(offset);
						}
					}
				}
			}
		}
	}

	/* 
	 * The method is called before the data memory is dumped to a $prefix_d.coe file. 
	 * It will replace the data written in labels with immediate values. 
	 */ 
	void replaceMemoryLabel() {
		// we may not need this depending on our design.

	}
	
	String makeBinary (String num) {
		String retVal = "";
		if (num.startsWith("0x")) {
			num = num.substring(num.lastIndexOf("x") + 1);
			for (int x=0; x<num.length(); x++) {
				String binary = Integer.toBinaryString(Character.digit(num.charAt(x),16));
				String leadZero = "";
				for (int y=0; y<4-binary.length(); y++) {
					leadZero += "0";
				}
				retVal += leadZero + binary;
			}						
		} else {
			retVal = Integer.toBinaryString(Integer.parseInt(num));
		}
		return retVal;
	}
	
	String padNumber (String pad, int length, int currLength, String currNum) {
		String padder="";
		for (int x=0; x<length-currLength; x++) {
			padder += pad;
		}
		return padder + currNum;
	}
	
	Integer findLabel (String label) {
		if (dataLabels.containsKey(label)) {
			return dataLabels.get(label);
		}
		if (textLabels.containsKey(label)) {
			return textLabels.get(label);
		} else {
			return null;
		}
	}
	/*
	 * You have to implement it for generating the machine 
	 * code from an Instruction object. 
	 */
	String generateCode(Instruction instruction) {
		String instructionCode = "";
		String[] args = {"", "", ""};

		//Operator
		if (debug) {
			System.out.println("Operator: " + instruction.operator);
		}
		instructionCode += instructions.getOpCode(instruction.operator);
		//Operands
		String operand;
		for (int i=0; i<instruction.operands.length; i++){
			operand = instruction.operands[i].name;
			if (debug) {
				System.out.print("operand" + i + " = " + operand + " is a ");
			}
			if (instruction.operands[i].getOperandType().equals("register")) {
				//Register
				if (debug) {
					System.out.println("Register");
				}
				args[i] = registers.getRegisterCode(operand);
			} else if (instruction.operands[i].name.startsWith("[")) {
				//Dereference	
				if (operand.contains("-") || operand.contains("+")) {
					if (debug) {
						System.out.println("Dereference with + or -");
					}
					args[i] = "1" + 
							  registers.getRegisterCode(operand.substring(operand.indexOf('%'), operand.indexOf('+'))); 

					String tmp = makeBinary(operand.substring(operand.indexOf('+')+1, operand.indexOf(']')));
					if (tmp.length() > 4) {
						memory.add(tmp, dataMemoryAddress);
						args[i] += "1";
						for (int x=0; x<4-Integer.toBinaryString(dataMemoryAddress).length(); x++) {
							args[i] += "0";
						}
						args[i] += Integer.toBinaryString(dataMemoryAddress);
						dataMemoryAddress++;
					} else {
						args[i] += "0";
						for (int x=0; x<4-tmp.length(); x++) {
							args[i] += "0";
						}
						args[i] += tmp;
					}
				} else {
					if (debug) {
						System.out.println("Dereference");
					}
					args[i] = "0" +
					  registers.getRegisterCode(operand.substring(operand.indexOf('%'), operand.indexOf(']')));
					for (int x=0; x<5; x++) {
						args[i] += "0";
					}
				}
			} else {
				if (operand.startsWith("addressl:") || operand.startsWith("addressd:")) {
					// Address
					if (debug) {
						System.out.println("Address");
					}
					if (operand.startsWith("addressl:")){

						/* TWOS COMPLEMENT VERSION */
//						String tmp = Integer.toHexString(Integer.parseInt(operand.substring(operand.lastIndexOf(":")+1)));
//						while (tmp.length() < 8 && Integer.parseInt(operand.substring(operand.lastIndexOf(":")+1)) < 0) {
//							if (Integer.parseInt(operand.substring(operand.lastIndexOf(":")+1)) < 0) {
//								tmp = "f" + tmp;
//							} else {
//								tmp = "0" + tmp;
//							}
//						}
//						if (Integer.parseInt(operand.substring(operand.lastIndexOf(":")+1)) < 0) {
//							tmp = "3" + tmp;
//						} else {
//							tmp = "0" + tmp;
//						}
						
						/* ALWAYS FORWARD VERSION */
						String tmp = Integer.toHexString(Math.abs(Integer.parseInt(operand.substring(operand.lastIndexOf(":")+1))));
						
						
						memory.add("0x"+tmp, dataMemoryAddress);
						args[i] = Integer.toBinaryString(dataMemoryAddress);
						dataMemoryAddress++;
					} else {
						args[i] = operand.substring(operand.lastIndexOf(":")+1);
					}
				} else {
					// Number
					if (debug) {
						System.out.println("Number");
					}
					args[i] = makeBinary(operand);
				}
			}
		}

		//Start Writting into .ceo FILE
		if (instruction.operands.length == 0) {
			//halt
			if (debug) {
				System.out.println("filling halt");
			}
			for (int x=0; x<12; x++) {
				instructionCode +="0";
			}
		} else if (instruction.operands.length == 1) {
			if (instruction.operator.equals("ba")) {
				//ba
				if (debug) {
					System.out.println("in ba with address: " + args[0]);
				}
				for (int x=0; x<12-args[0].length(); x++) {
					instructionCode +="0";
				}
				instructionCode += args[0];
			} else if (instruction.operator.equals("jmp") && !instruction.operands[0].getOperandType().equals("register")) {
				instructionCode += "1";
				instructionCode += padNumber("0", 11, args[0].length(), args[0]);
			} else {
				instructionCode += "011000000000";
			}
		} else if (instruction.operands.length == 2) {
			//ld, st
			if (instruction.operator.equals("ld") || instruction.operator.equals("st")) {
				String param1;
				String param2;
				if (instruction.operator.equals("ld")) {
					param1 = args[1];
					param2 = args[0];
				} else {
					param1 = args[0];
					param2 = args[1];
				}
				instructionCode += param1;
				int tmp = instructionCode.length();
				for (int x=0; x<17-tmp-param2.length(); x++) {
					instructionCode += "0";
				}
				instructionCode += param2;
			} else 
			//out, in
			if (instruction.operator.equals("out") || instruction.operator.equals("in")) {
				instructionCode += "01" + "00" + args[0] + "1" + args[1] + "0";
			} else 
			//mov
			if (instruction.operator.equals("mov")) {
				if (instruction.operands[0].getOperandType().equals("register")) {
					//all register
					instructionCode += "1";
					instructionCode += args[0] + args[1] + "00000";
				} else {
					instructionCode += "0";
					if (instruction.operands[0].name.startsWith("address")) {
						//address
						instructionCode += "1";
						instructionCode += args[1] + "1";
						for (int x=0; x<17-11-args[0].length(); x++) {
							instructionCode += "0";
						}
						instructionCode += args[0];
					} else {
						//immediate
						instructionCode += "0";
						instructionCode += args[1];
						if (args[0].length() > 6) {
							instructionCode += "1";
							if (instruction.operands[0].name.startsWith("0x")) {
								memory.add(instruction.operands[0].name, dataMemoryAddress);
							} else {
								memory.add("0x"+Integer.toHexString(Integer.parseInt(instruction.operands[0].name)), dataMemoryAddress);
							}
							instructionCode += padNumber("0", 6, Integer.toBinaryString(dataMemoryAddress).length(), Integer.toBinaryString(dataMemoryAddress));
							dataMemoryAddress++;
						} else {
							instructionCode += "0";
							instructionCode += padNumber("0", 6, args[0].length(), args[0]);
						}
					}
				}
			} else {
				System.out.println("WTF DID YOU DO?! 2 params");
			}
		} else {
			//swap
			if (instruction.operator.equals("swap")) {
				instructionCode += args[0] + args[1] + args[2] + "000";
			}
			//and or, nor, xnor
			if (instruction.operator.equals("and") ||
					instruction.operator.equals("or") ||
					instruction.operator.equals("nor") ||
					instruction.operator.equals("xnor")) {
				instructionCode += args[0] + args[2];
				if (instruction.operands[1].getOperandType().equals("register")) {
					instructionCode += "0";
					instructionCode += args[1] + "00";
				} else {
					instructionCode += "1";
					if (args[1].length()>4) {
						instructionCode += "1";
						memory.add("0x"+Integer.toHexString(Integer.parseInt(instruction.operands[1].name)), dataMemoryAddress);
						instructionCode += padNumber("0", 4, Integer.toBinaryString(dataMemoryAddress).length(), Integer.toBinaryString(dataMemoryAddress));
						dataMemoryAddress++;
					} else {
						instructionCode += "0";
						instructionCode += padNumber("0", 4, args[1].length(), args[1]);
					}
				}
			}
			//add, sub
			if (instruction.operator.equals("add") || instruction.operator.equals("sub")) {
				if (instruction.operands[1].getOperandType().equals("register")) {
					//all registers
					instructionCode += "1";
					instructionCode += args[0] + args[1] + args[2] + "00";
				} else {
					//immediate or address
					instructionCode += "0";
					if (args[1].length() > 4) {
						//address
						instructionCode += "1";
						memory.add("0x"+Integer.toHexString(Integer.parseInt(instruction.operands[1].name)), dataMemoryAddress);
						instructionCode += padNumber("0", 4, Integer.toBinaryString(dataMemoryAddress).length(), Integer.toBinaryString(dataMemoryAddress));
						dataMemoryAddress++;
					} else {
						//register
						instructionCode += "0";
						instructionCode += padNumber("0", 4, args[1].length(), args[1]);
					}
					instructionCode += args[0] + args[2];
				}
			}
			//bne, be, bg, bge, bl, ble
			if (instruction.operator.equals("be") || instruction.operator.equals("bne") ||
					instruction.operator.equals("bg") || instruction.operator.equals("bge") ||
					instruction.operator.equals("bl") || instruction.operator.equals("ble")) {
				instructionCode += args[0] + args[1];
				instructionCode += padNumber("0", 6, args[2].length(), args[2]);
			}
			//sll, srl
			if (instruction.operator.equals("sll") || instruction.operator.equals("srl")) {
				instructionCode += args[0] + args[2];
				if (args[1].length() > 5) {
					instructionCode += "1";
					memory.add("0x"+Integer.toHexString(Integer.parseInt(instruction.operands[1].name)), dataMemoryAddress);
					instructionCode += padNumber("0", 5, Integer.toBinaryString(dataMemoryAddress).length(), Integer.toBinaryString(dataMemoryAddress));
					dataMemoryAddress++;
				} else {
					instructionCode += "0";
					instructionCode += padNumber("0", 5, args[1].length(), args[1]);
				}
			}
		}
		if (debug) {
			System.out.println("final instruction code: " + instructionCode);
		}
		return instructionCode;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			JayaLeeAssembler ourAssembler = new JayaLeeAssembler(args);
			ourAssembler.AssembleCode(args);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
